iT邦幫忙

2023 iThome 鐵人賽

DAY 30
1

最後一天,如同昨天說的,今天至少會把IMA三個extension的test全部通過。

RV64M TEST
首先我們先試著透過原先建立好的環境試著跑看看RV64M
https://ithelp.ithome.com.tw/upload/images/20231015/20162436VwHx0cSlpR.png
執行結果如下,目前看來是32bit的div和rem有問題。
根據log判斷是sign-extension有問題,檢查發現原先除法時會取後32bit,但資料型態還是int64,導致負數沒有正確被sign-extension,因此修改為以下程式碼。

reg[rd] = sext(((int32_t)(reg[rs1]  & 0xffffffff)) / ((int32_t)(reg[rs2] & 0xffffffff)),32);

先取完32bit之後當成int32進行除法,再將除法結果做sign-extension。

修改之後遇到第二個問題,執行下去會報SEGFAULT floatpoint exception,判斷應該與特例有關,檢查發現在remw和divw時還是根據INT64_M去判斷MIN/-1的狀況,修改為INT32_M。

修改後,RV64M Pass。
https://ithelp.ithome.com.tw/upload/images/20231015/201624366BXDvgFxvX.png

RV64A TEST
https://ithelp.ithome.com.tw/upload/images/20231015/20162436kN7sgYXQ4K.png
執行RV64A Test,發現沒有遇到BUG,截至目前,我們已經完成一個支援RV64bit IMA的模擬器,並能夠通過RISC-V Test的RV64IMA。

Linux RUN
昨天文章有提到,要把Linux跑起來,首先要有load device tree binary的方法,再來要有一個假的UART來充當console。

我們先產出一個DTB,在先前安裝Spike時我們已經有Device tree compiler,因此我們只要自己寫一個DTS即可編譯出一個DTB。

範例的DTS如下,我們只留下我們需要的部分即可,像interrupt等目前沒有實現的部分可以先不要。 ref

/dts-v1/;
/ {
	#address-cells = <0x02>;
	#size-cells = <0x02>;
	compatible = "riscv-minimal-nommu";
	model = "riscv-minimal-nommu,qemu";

	chosen {
		bootargs = "earlycon=uart8250,mmio,0x10000000,1000000 console=hvc0";
	};

	memory@80000000 {
		device_type = "memory";
		reg = <0x00 0x80000000 0x00 0x3ffc000>;
	};

	cpus {
		#address-cells = <0x01>;
		#size-cells = <0x00>;
		timebase-frequency = <0xf4240>;

		cpu@0 {
			phandle = <0x01>;
			device_type = "cpu";
			reg = <0x00>;
			status = "okay";
			compatible = "riscv";
			riscv,isa = "rv64ima";
			mmu-type = "riscv,none";

			interrupt-controller {
				#interrupt-cells = <0x01>;
				interrupt-controller;
				compatible = "riscv,cpu-intc";
				phandle = <0x02>;
			};
		};

		cpu-map {

			cluster0 {

				core0 {
					cpu = <0x01>;
				};
			};
		};
	};

	soc {
		#address-cells = <0x02>;
		#size-cells = <0x02>;
		compatible = "simple-bus";
		ranges;

		uart@10000000 {
			clock-frequency = <0x1000000>;
			reg = <0x00 0x10000000 0x00 0x100>;
			compatible = "ns16850";
		};

		poweroff {
			value = <0x5555>;
			offset = <0x00>;
			regmap = <0x04>;
			compatible = "syscon-poweroff";
		};

		reboot {
			value = <0x7777>;
			offset = <0x00>;
			regmap = <0x04>;
			compatible = "syscon-reboot";
		};
	};
};

將以上內容儲存為文件ALISS.dts,並輸入

dtc -I dts -O dtb -o ALISS.dtb ALISS.dts

同時我們實現DTB loader

bool loadDTB(const char* filename, uint64_t dtb_addr )
    {
        // Open the file
        FILE* file = fopen(filename, "rb");
        if (file == NULL) {
            perror("Failed to open the file");
            exit(EXIT_FAILURE);
        }

        // Get the file size
        fseek(file, 0, SEEK_END);
        long file_size = ftell(file);
        rewind(file);

        // Allocate memory to store the file content
        uint8_t* file_data = (uint8_t*)malloc(file_size);
        if (file_data == NULL) {
            perror("Memory allocation failed");
            fclose(file);
            exit(EXIT_FAILURE);
        }

        // Read the file content
        size_t bytes_read = fread(file_data, 1, file_size, file);
        if (bytes_read != file_size) {
            perror("Failed to read the file");
            free(file_data);
            fclose(file);
            exit(EXIT_FAILURE);
        }

        // Use memcpy to copy the file content into memory
        memcpy(memory + dtb_addr, file_data, file_size);

        // Close the file and free memory
        fclose(file);
        free(file_data);

        return 0;
    };

最後,為了讓Linux對外輸出可以顯示在我們的螢幕上,我們需要實作Uart的功能,透過在Load/Store指令的地方判斷是否有碰到Uart來實作,實作方法如下。

            case 0x3:  //LOAD
            {
                uint64_t rd = ((insn >> 7) & 0x1f);
                uint64_t rs1 = ((insn  >> 15) & 0x1f);
                uint64_t imm = ((insn >> 20) & 0xfff);

               if(reg[rs1] + sext(imm,12) == 0x10000005)
               {
                   reg[rd] = 0x60;
                   break;
               }

            case 0x23: //STORE
            {
                uint64_t rs1 = ((insn  >> 15) & 0x1f);
                uint64_t rs2 = ((insn  >> 20) & 0x1f);

                uint64_t imm = ( ( insn & 0xfe000000 ) >> 20 ) | // [11:5]
                               ( ( insn >> 7 ) & 0x1f ); //[4:0]

                if(reg[rs1] + sext(imm,12) == 0x10000000)
                {
                    printf("%c",(uint8_t)reg[rs2]);
                    break;
                }

以上完成後,想像上應該就能夠跑Linux啦,GOGO!

Linux Run
https://ithelp.ithome.com.tw/upload/images/20231015/20162436mbosyV5nPX.png
有輸出內容代表Uart沒問題,但看起來是DTB的位置有問題,不過這個部分待我之後再來Debug,先簡單總結一下這三十天的心得。


最後的碎碎念,下次一定

https://ithelp.ithome.com.tw/upload/images/20231015/20162436QpotmtBdKj.jpg

感謝這三十天來所有路過的看官,以及追蹤這系列文章的朋友們。雖然最終沒有達到一開始所訂下的RV64IMA Linux的目標,但至少我們順利完成一個RV64IMA的模擬器,且能夠順利通過RISC-V官方的Test。中間真的幾度想要放棄,特別是連假的時候燒到38度在床上兩天的時候。幸好最後還是撐到完賽了。

最開始只是跟朋友聊天的時候一時興起,想要製作一個叫做ALISS的專案,最後不小心就參賽、進而催生出了這個模擬器。原本參賽前想說三十天而已,且是我平常有在接觸、較為熟悉的內容,難度應該不高。但最後卻發現要好好撐完這三十天真是困難,一直到差不多二十天的時候才開始漸漸習慣。

對我來說,鐵人賽最困難的地方比起把專案完成,怎麼樣完整地說一個故事才是更為困難的。我常常在要描述A的時候發現需要補充B,而在描述B的時候又需要描述CDE,怎麼樣碎片化知識但又要說的讓人聽得懂真的很難,非常佩服那些可以精準地用文字描述心裡想法的前輩們。而我也高估了自己下班之後的生產力XD,還記得剛開賽前兩天是假日所以有較多的時間及精力可以去寫文章,但一到上班日我就發現自己的腦細胞好像死光,下完班之後就虛脫不已,常常要睡一下之後才能好好地產出。

但同時,鐵人賽也讓我得到很多,我一方面學習了過去只有使用,但不知道怎麼架設的Jenkins, Gtest等環境,也學習怎麼用VSCode連接docker。同時也因為參加這次比賽,讓我比以往還要更認真的看RISC-V的Spec,我第一次那麼認真的研究每一道指令的每一個bit,也讓我手寫in-line assembly的能力有所上升XD。最後,我也慢慢在學習怎麼樣去分享知識給別人,在工作中需要解釋一些domain knowledge給其他部門的同事時也體感更為順利,這是我參賽前沒有預料到,但非常好的收穫。

下次還要不要參賽,我目前還沒有想法XD 我覺得不是分享已知的東西,而是藉這個機會開始學習一些不擅長的東西或許也不錯,例如怎麼製作一個遊戲等等的,但這些就讓明年的我來煩惱好了。謝謝收看,珍重再見。

Yoga


上一篇
Day 29 - Debug, RV64I Test Pass
系列文
從零開始的RISC-V ISA Simulator (Another Little RISC-V ISA Simulator)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
高魁良
iT邦新手 1 級 ‧ 2023-10-16 13:35:07

然後之後你就會發現有人在面試的時候,很靦腆得說他看過你的文章 XD。
恭喜完賽!

yoga57894 iT邦新手 5 級 ‧ 2023-10-16 22:42:00 檢舉

感謝~~ 期待有那麼一天XD

我要留言

立即登入留言